﻿using System.Collections.Generic;
using System.Threading;
using S7.Net;
using S7PlcDataCollector.Models;
using S7PlcWrapper;

namespace S7PlcDataCollector.Service.PlcLink
{
    internal class PlcDataHelper
    {
        #region 线程

        private Thread _threadWorker;
        private bool _threadWorking;
        private readonly object _threadWorkingLockHelper = new object();

        private bool IsThreadWorking
        {
            get
            {
                bool ret;
                lock (_threadWorkingLockHelper)
                {
                    ret = _threadWorking;
                }

                return ret;
            }
            set
            {
                lock (_threadWorkingLockHelper)
                {
                    _threadWorking = value;
                }
            }
        }

        #endregion

        #region 队列

        private readonly Queue<S7PlcVariableEntity> _variableQueue;
        private readonly Semaphore _variableSemaphore;

        #endregion

        private readonly S7PlcHelper _plcHelper;
        private readonly IPlcDataObserver _plcDataObserver;

        public PlcDataHelper(S7PlcHelper plcHelper, IPlcDataObserver plcDataObserver)
        {
            _plcHelper = plcHelper;
            _plcDataObserver = plcDataObserver;

            _variableQueue = new Queue<S7PlcVariableEntity>();
            _variableSemaphore = new Semaphore(0, short.MaxValue);
        }

        public void Start()
        {
            IsThreadWorking = true;
            _threadWorker = new Thread(DoWork)
            {
                IsBackground = true
            };
            _threadWorker.Start();
        }

        public void Stop()
        {
            IsThreadWorking = false;
            AddVariable(null);
            if (_threadWorker != null)
            {
                _threadWorker.Join();
                _threadWorker = null;
            }

            ClearVariable();
        }

        #region 队列操作

        public void AddVariable(S7PlcVariableEntity variable)
        {
            if (IsThreadWorking)
            {
                lock (_variableQueue)
                {
                    _variableQueue.Enqueue(variable);
                }

                _variableSemaphore.Release();
            }
        }

        private S7PlcVariableEntity PickVariable()
        {
            S7PlcVariableEntity variable = null;
            lock (_variableQueue)
            {
                if (_variableQueue.Count > 0)
                {
                    variable = _variableQueue.Peek();
                    _variableQueue.Dequeue();
                }
            }

            return variable;
        }

        private void ClearVariable()
        {
            lock (_variableQueue)
            {
                _variableQueue.Clear();
            }
        }

        #endregion

        /// <summary>
        /// 线程执行方法
        /// </summary>
        private void DoWork()
        {
            while (IsThreadWorking)
            {
                if (_variableSemaphore.WaitOne(1))
                {
                    GetVariableData(PickVariable());
                }
            }
        }

        private void GetVariableData(S7PlcVariableEntity variable)
        {
            if (variable == null || !_plcHelper.IsConnected)
            {
                return;
            }

            switch (variable.VariableTypeEnum)
            {
                case VarType.String:
                    ReadPlcString(variable);
                    break;
                case VarType.S7String:
                    ReadPlcS7String(variable);
                    break;
                default:
                    ReadPlcObject(variable);
                    break;
            }
        }

        private void ReadPlcString(S7PlcVariableEntity variable)
        {
            string value;
            if (_plcHelper.ReadString(variable.Variable, out value))
            {
                ReceivePlcDataOk(variable, value);
            }
            else
            {
                ReceivePlcDataError(variable, _plcHelper.LastErrorMsg);
            }
        }

        private void ReadPlcS7String(S7PlcVariableEntity variable)
        {
            string value;
            if (_plcHelper.ReadS7String(variable.Variable, out value))
            {
                ReceivePlcDataOk(variable, value);
            }
            else
            {
                ReceivePlcDataError(variable, _plcHelper.LastErrorMsg);
            }
        }

        private void ReadPlcObject(S7PlcVariableEntity variable)
        {
            object value;
            if (_plcHelper.ReadVariable(variable.Variable, out value))
            {
                var val = GetVariableValue(variable.VariableTypeEnum, value);
                if (val == null)
                {
                    ReceivePlcDataError(variable, $@"不支持的数据类型[{variable.VariableTypeEnum}]");
                }
                else
                {
                    ReceivePlcDataOk(variable, val);
                }
            }
            else
            {
                ReceivePlcDataError(variable, _plcHelper.LastErrorMsg);
            }
        }

        private static object GetVariableValue(VarType varType, object var)
        {
            switch (varType)
            {
                case VarType.Bit:
                    return (bool) var;
                case VarType.Byte:
                    return (byte) var;
                case VarType.Word:
                    return (ushort) var;
                case VarType.DWord:
                    return (uint) var;
                case VarType.Int:
                    return (short) var;
                case VarType.DInt:
                    return (int) var;
                case VarType.Real:
                    return ((uint)var).ConvertToFloat();
                case VarType.LReal:
                    return (double) var;
                default:
                    return null;
            }
        }

        private void ReceivePlcDataOk(S7PlcVariableEntity variable, object data)
        {
            _plcDataObserver?.ReceivePlcData(variable, new PlcDataResult()
            {
                Ok = true,
                Data = data
            });
        }

        private void ReceivePlcDataError(S7PlcVariableEntity variable, string errorMessage)
        {
            _plcDataObserver?.ReceivePlcData(variable, new PlcDataResult()
            {
                Ok = false,
                ErrorMessage = errorMessage
            });
        }
    }
}
